使用 Kotlin 重写 AOSP 日历应用
两年前,Android 开源项目 (AOSP) 应用团队开始使用 Kotlin 替代 Java 重构 AOSP 应用。之所以重构主要有两个原因: 一是确保 AOSP 应用能够遵循 Android 最佳实践,另外则是提供优先使用 Kotlin 进行应用开发的良好范例。Kotlin 之所以具有强大的吸引力,原因之一是其简洁的语法,很多情况下用 Kotlin 编写的代码块的代码数量相比于功能相同的 Java 代码块要更少一些。此外,Kotlin 这种具有丰富表现力的编程语言还具有其他各种优点,例如:
空安全: 这一概念可以说是根植于 Kotlin 之中,从而帮助避免破坏性的空指针异常;
并发: 正如 Google I/O 2019 中关于 Android 的描述,结构化并发 (structured concurrency) 能够允许使用协程简化后台的任务管理; 兼容 Java: 尤其是在这次的重构项目中,Kotlin 与 Java 语言的兼容性能够让我们一个文件一个文件地进行 Kotlin 转换。
Android 开源项目 (AOSP) 应用
https://android.googlesource.com/platform/packages/apps/Kotlin
https://kotlinlang.org/Google I/O 2019
https://developer.android.google.cn/kotlin/first
AOSP 桌面时钟应用的转换过程
https://medium.com/androiddevelopers/re-writing-the-aosp-deskclock-app-in-kotlin-76c836370cb
Android Studio
https://developer.android.google.cn/studio从 Java 到 Kotlin 的自动转换工具
https://developer.android.google.cn/kotlin/add-kotlin#convertCompatibility Test Suite (CTS)
https://source.android.google.cn/compatibility/cts
自动转换之后的步骤
上面提到,在使用自动转换工具之后,有一些反复出现的问题需要手动定位解决。在 AOSP 桌面时钟文章中,详细介绍了其中遇到的一些问题以及解决方法。如下列出了一些在进行 AOSP 日历转换过程中遇到的问题。
用 open 关键词标记父类
class MonthByWeekAdapter(context: Context?, params:
HashMap<String?, Int?>) : SimpleWeeksAdapter(context as Context, params) {//方法体}
open class SimpleWeeksAdapter(context: Context, params: HashMap<String?, Int?>?) {//方法体}
override 修饰符
同样地,子类中覆盖父类的方法也必须使用 override 修饰符来进行标记。在 Java 中,这是通过 @Override 注解来实现的。然而,虽然在 Java 中有相应的注解实现版本,但是自动转换过程中并没有为 Kotlin 方法声明中添加 override 修饰符。解决的办法是在所有适当的地方手动添加 override 修饰符。
覆写父类中的属性
type of *property name* doesn’t match the type of the overridden var-property
public class Parent {
int num = 0;
}
class Child extends Parent {
String num = "num";
}
class Parent {
var num: Int = 0
}
class Child : Parent() {
var num: String = "num"
}
这个问题很有意思,目前我们通过在子类中对变量重命名来规避了这个冲突。上面的 Java 代码会被 Android Studio 目前提供的代码转换器转换为有问题的 Kotlin 代码,这甚至被报告为是一个 bug 了。
被报告为是一个 bug
https://youtrack.jetbrains.com/issue/KTIJ-8621
import 语句
暴露成员变量
默认情况下,Kotlin 会自动地为类中的实例变量生成 getter 和 setter 方法。然而,有些时候我们希望一个变量仅仅只是一个简单的 Java 成员变量,这可以通过使用 @JvmField 注解来实现。
@JvmField 注解的作用是 "指示 Kotlin 编译器不要为这个属性生成 getter 和 setter 方法,并将其作为一个成员变量允许其被公开访问"。这个注解在 CalendarData 类中特别有用,它包含了两个 static final 变量。通过对使用 val 声明的只读变量使用 @JvmField 注解,我们确保了这些变量可以作为成员变量被其他类访问,从而实现了 Java 和 Kotlin 之间的兼容性。
@JvmField 注解
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-field/CalendarData 类
https://android.googlesource.com/platform/packages/apps/Calendar/+/42e4b43133c4f866e0729438fb38bebc6d03b0a4/src/com/android/calendar/CalendarData.ktval
https://kotlinlang.org/docs/basic-syntax.html#variables
对象中的静态方法
Kotlin 的文档
https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-methodsUtils 文件
https://android.googlesource.com/platform/packages/apps/Calendar/+/42e4b43133c4f866e0729438fb38bebc6d03b0a4/src/com/android/calendar/Utils.kt
性能评估分析
所有的基准测试都是在一台 96 核、176 GiB 内存的机器上进行的。本项目中分析用到的主要指标有所减少的代码行数、目标 APK 的文件大小、构建时间和首屏从启动到显示的时间。在对上述每个因素进行分析的同时,我们还收集了每个参数的数据并以表格的方式进行了展示。
减少的代码行数
AOSP 桌面时钟 https://medium.com/androiddevelopers/re-writing-the-aosp-deskclock-app-in-kotlin-76c836370cb
目标 APK 大小
Proguard https://developer.android.google.cn/studio/build/shrink-code R8 https://r8.googlesource.com/r8
编译时间
Java 和 Kotlin 的区别
https://www.educba.com/java-vs-kotlin/
Kotlin 和 Java 在编译时间上的对比 https://medium.com/keepsafe-engineering/kotlin-vs-java-compilation-speed-e6c174b39b5d
首屏显示的时间
方法 https://developer.android.google.cn/topic/performance/vitals/launch-time#time-initial
结论
欢迎您通过下方二维码向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!
免费中文系列课程下载
系统地学习使用 Kotlin 进行 Android 开发
☟ 即刻了解课程详情 ☟
推荐阅读